package com.bhh.nio.chat;

import lombok.extern.slf4j.Slf4j;

import java.io.Closeable;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Set;

/**
 * @author bhh
 * @description 基于BIO模型, 客户端实现
 * 和 UserInputHandler类
 * 和 ChatServer 类 + ChatHandler类配合使用
 * @date Created in 2021-04-07 16:48
 * @modified By
 */
@Slf4j
public class NIOChatClient {
    private static final String SERVER_IP = "127.0.0.1";
    private static final int SERVER_PORT = 8888;
    private final String QUIT = "quit";
    private static final int BUFFER = 1024;

    private String host;
    private int port;
    private SocketChannel client;
    private ByteBuffer rBuffer;
    private ByteBuffer wBuffer;
    private Charset charset;
    private Selector selector;

    NIOChatClient(String host, int port) {
        this.host = host;
        this.port = port;
        rBuffer = ByteBuffer.allocate(BUFFER);
        wBuffer = ByteBuffer.allocate(BUFFER);
        charset = Charset.forName("UTF-8");
    }

    NIOChatClient() {
        this(SERVER_IP, SERVER_PORT);
    }

    /**
     * 客户端断开连接
     *
     * @param msg
     * @return
     */
    public boolean quit(String msg) {
        return QUIT.equals(msg);
    }

    /**
     * writer流关闭, 若是关闭socket中的任一流, socket流与socket有关的流都会关闭
     *
     * @param closeable
     */
    private void close(Closeable closeable) {
        try {
            closeable.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 整合各个封装的方法完成功能
     */
    public void start() {

        try {
            // 初始化 SocketChannel
            client = SocketChannel.open();
            client.configureBlocking(false);

            // 初始化 Selector
            selector = Selector.open();
            // 进行注册
            client.register(selector, SelectionKey.OP_CONNECT);
            // 进行连接
            client.connect(new InetSocketAddress(host, port));

            while (true) {
                // select() 返回处于指定事件的 channel 数量
                //TODO 认为可以添加 select() > 0 为判断条件
                selector.select();
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
//                System.out.println("selectionKeys = ");
//                selectionKeys.stream().forEach(System.out::println);
                for (SelectionKey selectionKey : selectionKeys) {
                    // 处理事件
                    handles(selectionKey);
                }
                // 集合中清除 SelectionKey 就相当于是 清除了channel的状态,
                // 当下一次channel再一次出现指定事件, select() 就可以再次发现
                selectionKeys.clear();
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            close(selector);
        }
    }

    /**
     * 处理 事件
     *
     * @param selectionKey 事件
     */
    private void handles(SelectionKey selectionKey)  throws IOException {

        SocketChannel client = (SocketChannel) selectionKey.channel();
        // CONNECT事件, 客户端连接服务端, 处理连接, 处理用户输入, 处理注册READ事件
        if (selectionKey.isConnectable()) {
            if (client.isConnectionPending()) {
                // 注册 READ事件到 Selector
                client.register(selector, SelectionKey.OP_READ);
                // 完成连接
                client.finishConnect();
                // 处理用户输入
                new Thread(new UserInputHandler(this)).start();
            }
        }
        // READ事件, 客户端读取数据
        else if (selectionKey.isReadable()) {
            // 读取数据
            String msg = receive(client);
            // 判空
            if (msg.isEmpty()) {
                // 服务器异常
                close(selector);
            } else {
                log.info(String.valueOf(msg));
            }
        }
    }

    private String receive(SocketChannel client) throws IOException {
        // 转换为写模式
        rBuffer.clear();
        while (client.read(rBuffer) > 0) {
        }
        // 转换为读模式
        rBuffer.flip();
        return String.valueOf(charset.decode(rBuffer));
    }

    /**
     * 发送消息到服务器, 即调用write()方法
     *
     * @param msg
     */
    public void send(String msg) throws IOException {
        if (msg.isEmpty()) {
            log.info("请重新输入");
            return;
        }
        // 转换为 写模式
        wBuffer.clear();
        wBuffer.put(charset.encode(msg));
        // 转换为 读模式
        wBuffer.flip();

        while (wBuffer.hasRemaining()) {
            client.write(wBuffer);
        }

        // 退出
        if (QUIT.equals(msg)) {
            close(selector);
        }
    }

    public static void main(String[] args) {
        NIOChatClient client = new NIOChatClient(SERVER_IP, 7777);
        client.start();
    }
}
